本文作者
作者:yechaoa
链接:
https://juejin.cn/post/7354940230301696009
本文由作者授权发布。
依赖替换不只是运用在依赖管理上,多数情况下,是来解决模块化架构下的多人协作问题、开发效率和编译提速问题。
1.1、模块化
1.2、多模块的问题
1.3、解法
implementation 'com.github.yechaoa.GradleX:plugin:1.5'
本地开发则切换到工程project的依赖方式:
implementation project(':plugin')
通过这种灵活切换可以实现每个开发同学一个壳加一个模块的本地开发模式,所以,模块化的架构不仅提升了可维护性和开发效率,也大大提升了构建效率。
2.1、if else
这里以我另一个开源库YUtils为例,把它作为一个模块依赖到GradleX项目中来。
https://github.com/yechaoa/YUtils
2.1.1、配置
useLocal=true
def localProperties = new Properties()
file("../local.properties").withInputStream { localProperties.load(it) }
def useLocal = localProperties.getProperty('useLocal', 'false').toBoolean()
if (useLocal) {
implementation project(':yutilskt')
} else {
implementation 'com.github.yechaoa.YUtils:yutilskt:3.4.0'
}
上面代码表示,有useLocal属性且值为true的情况下,使用远端依赖,否则使用本地依赖。
还有一个小技巧,为了方便其他地方也使用local.properties中的useLocal属性,可以把这个读取操作抽成一个方法,除了可以复用之外,local.properties文件的路径参数也是固定不变的了,方便很多。
./gradlew assembleDebug -PuseLocal=true
命令行的属性(-P)优先级是要高于Gradle属性(xxx.properties)的。 在云编译的时候可以改为false。
def localProperties = new Properties()
file("local.properties").withInputStream { localProperties.load(it) }
def useLocal = localProperties.getProperty('useLocal', 'false').toBoolean()
if (hasProperty('useLocal') && getProperty('useLocal').toBoolean()) {
include ':yutilskt'
}
include默认会从项目根目录去找,如果模块是独立的存储位置,即模块的位置不是在项目的根目录下,那么引进来的时候需要指定一下路径projectDir,告诉Gradle去哪找它。
if (hasProperty('useLocal') && getProperty('useLocal').toBoolean()) {
include ':yutilskt'
project(':yutilskt').projectDir = new File('../YUtils/yutilskt')
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
ext.kotlin_version = '1.7.10'
所以现在就会有一个问题,当yutilskt模块include引入到GradleX项目中的时候,如果GradleX项目中没有声明kotlin_version的话,就会出现yutilskt模块中的kotlin标准库找不到版本而导致编译失败的情况。
plugins {
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
所以这种情况下,子模块yutilskt中的kotlin_version属性就也要在GradleX项目中也定义一份。
kotlin_version=1.7.10
在子模块和壳工程有共享配置的情况下,推荐使用include,比较独立的话用includeBuilding。
2.1.3、效果
.
├── app
├── build
├── buildSrc
├── gradle
├── plugin
└── ...
我们运行一下再来看看:
2.1.4、版本控制
2.2、substitute
在前面的第6章里,我们详细介绍了依赖解析相关的内容,依赖替换跟依赖解析有点像,它们都是依赖管理的一部分,都是用来定义依赖的能力,它们最大的区别在于,依赖替换可以允许项目和依赖项的相互替换,即源码和二进制依赖的相互替换。
https://juejin.cn/post/7215579793261117501
2.2.1、语法
substitute <dependency> using <replacement>
其中:
要替换的依赖项; 替换后的依赖项;
后者替换前者; Gradle 6.6版本及以后,using方法替代了with方法。
substitute project(':yutilskt') using module('com.github.yechaoa.YUtils:yutilskt:3.4.0')
project():项目,表示源码依赖,格式为:
project(':path')
module():模块,表示二进制依赖,即发布远端通过GAV引入的依赖,比如AAR,格式为:
module('{group}:{module}:{version}')
2.2.2、使用
resolutionStrategy.dependencySubstitution {
// 把yutilskt替换成远端依赖
substitute project(':yutilskt') using module('com.github.yechaoa.YUtils:yutilskt:3.4.0')
// 把yutilskt:3.4.0替换成源码依赖
substitute module('com.github.yechaoa.YUtils:yutilskt:3.4.0') using project(':yutilskt')
// 把yutilskt:3.3.3替换成yutilskt:3.4.0
substitute module('com.github.yechaoa.YUtils:yutilskt:3.3.3') using module('com.github.yechaoa.YUtils:yutilskt:3.4.0')
}
将源码依赖替换成远端依赖; 将远端依赖替换成源码依赖; 将远端依赖替换成另一个版本;
2.2.3、实践
resolutionStrategy.dependencySubstitution {
// 把yutilskt:3.4.0替换成源码依赖
substitute module('com.github.yechaoa.YUtils:yutilskt:3.4.0') with project(':yutilskt')
}
在local.properties中配置:
useLocal=true
./gradlew assembleDebug -PuseLocal=true
添加替换的判断校验
def localProperties = new Properties()
file("../local.properties").withInputStream { localProperties.load(it) }
def useLocal = localProperties.getProperty('useLocal', 'false').toBoolean()
resolutionStrategy.dependencySubstitution {
if (useLocal) {
substitute module('com.github.yechaoa.YUtils:yutilskt:3.4.0') using project(':yutilskt')
}
}
implementation 'com.github.yechaoa.YUtils:yutilskt:3.4.0'
毕竟你得先有依赖,才能给你替换啊对吧。
配置include
if (useLocal) {
include ':yutilskt'
project(':yutilskt').projectDir = new File('../YUtils/yutilskt')
}
2.2.4、效果
2.3、进阶
settings.gradle文件中的include配置。 build.gradle文件中的substitute配置。
app.yutilskt=yutilskt
然后模块clone到本地的路径保持跟壳工程路径同一层级,这样就可以在固定的路径找到local.properties文件中定义的模块,从而进行切换操作了。
2.3.1、拆解诉求
settings.gradle文件中的include配置我们可以Hook Gradle的生命周期,拿到Settings对象进行动态添加include; build.gradle文件中的substitute配置我们同样可以Hook拿到Configuration对象进行添加; 定义一个协议文件,yml、xml、json啥的都可以,其中包含一些常用字段; 然后Settings对象和Configuration对象对这个文件进行解析、添加。
对Gradle生命周期不熟的,可以去看看前面的第4章。 https://juejin.cn/post/7170684769083555877
2.3.2、定义协议文件
[
{
"useLocal": true,
"moduleName": "yutilskt",
"modulePath": "../YUtils/yutilskt",
"moduleGav": "com.github.yechaoa.YUtils:yutilskt:3.4.0"
}
]
2.3.3、编写插件
class UseLocalPlugin implements Plugin<Settings> {
@Override
void apply(Settings settings) {
}
}
因为include是在settings初始化的阶段,所以这里直接使用Settings对象,而不是Project对象。
2.3.4、Hook Settings
class UseLocalPlugin implements Plugin<Settings> {
@Override
void apply(Settings settings) {
// Gradle初始化,此时可以获取到Settings对象
settings.gradle.settingsEvaluated {
// 加载useLocal.json文件
def useLocalFile = new File(settings.getRootDir(), "useLocal.json")
if (!useLocalFile.exists()) {
println("useLocal.json文件不存在")
return
}
def useLocalText = useLocalFile.text
// 解析JSON文本
def jsonSlurper = new JsonSlurper()
def useLocalData = jsonSlurper.parseText(useLocalText)
// include
useLocalData.each { item ->
if (item.useLocal) {
settings.include(":${item.moduleName}")
settings.project(":${item.moduleName}").projectDir = new File(item.modulePath)
}
}
// 依赖替换
SwitchAarToCode(settings.gradle, useLocalData)
}
}
}
在settings.gradle.settingsEvaluated { }回调中可以拿到初始化好的settings对象; 解析我们的协议json文件; 再通过settings对象进行include; 最后调用SwitchAarToCode()方法进行依赖替换。
2.3.5、Hook Configuration
def SwitchAarToCode(gradle, useLocalData) {
// 所有Project对象evaluate完毕之后,会回调gradle.projectsEvaluated
gradle.projectsEvaluated {
gradle.allprojects { pro ->
// 这个app可以根据你的项目名称来判断
if (pro.name == "app") {
pro.configurations.all { configuration ->
configuration.resolutionStrategy.dependencySubstitution { substitutions ->
useLocalData.each { item ->
if (item.useLocal) {
substitute module(item.moduleGav) using substitutions.project(":${item.moduleName}")
}
}
}
}
}
}
}
}
依赖解析是在配置阶段,所以我们需要在gradle.projectsEvaluated { }回调中拿到project的configuration对象;
if (pro.name == "app") 这个app大家可以根据自己的主项目名称来判断; 最后调用substitute 进行依赖替换。
2.3.6、依赖插件
apply plugin: UseLocalPlugin
2.3.7、效果
2.3.8、小结
实际项目中,useLocal.json文件可以先内置一些常用的模块,并把所有的useLocal默认设置为false,然后先提交一份,再把文件添加到.gitignore里,这样其他同学只要修改一下useLocal的值然后sync一下,就能快乐的进行源码切换了。
GitHub:
https://github.com/yechaoa/GradleX
相关文档
Customizing resolution of a dependency directly
https://docs.gradle.org/current/userguide/resolution_rules.html#sec:dependency_substitution_rules
【Gradle-4】Gradle的生命周期
https://juejin.cn/post/7170684769083555877
【Gradle-6】一文搞懂Gradle的依赖管理和版本决议
https://juejin.cn/post/7215579793261117501